package com.boarsoft.boar.config.service;

import com.boarsoft.bean.ReplyInfo;
import com.boarsoft.boar.config.lock.LockInfo;
import com.boarsoft.boar.config.lock.biz.LockInfoBiz;
import com.boarsoft.boar.config.lock.listener.CommonLockListener;
import com.boarsoft.common.dao.PagedResult;
import com.boarsoft.common.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁组件主要是分布式锁服务的客户端API，功能如下：
 * 1.调用分布式锁服务获取锁：业务代码在读写某个共享资源前，可以使用分布式锁组件来调用分布式锁服务，注册一个分布式锁，并将获得的“锁”写入本地缓存，以便在并发时提升性能。
 * 2.定时抢占锁：允许业务代码注册定时器来抢占某个URL的锁。
 * 3.缓存获取到的分布式锁：业务代码在访问某个共享资源前，先检查缓存中是否有相应的“锁”，有则表示可以执行相应调用，反之则尝试调用分布式锁服务获取锁。
 * 4.释放分布式锁：业务代码可以在访问某个共享资源后，主动释放锁。
 * 5.定时清除锁缓存：被缓存的锁可以加上失效时间，定时清除。
 * 6.锁变更通知：当业务代码抢占某个锁失败时，可以选择阻塞等待或注册事件回调通知，分布式锁服务将在锁可用时，通知业务代码重新尝试抢占此锁。
 * 7.锁降级：调用分布式锁服务，降级持有的锁。
 * 应用可以针对某个资源（URL）向分布式锁服务申请三种锁，分别是共享读锁、写锁（可共享读）、独占锁（不可共享读）。其它应用在读取此资源时，需要先到分布式锁服务抢占这个资源的读锁。需要写时则需要抢占写锁。
 * 读锁是共享锁，允许多个客户端同时持有。
 * 写锁只允许一个客户端持有，但允许其它客户端共享读取。
 * 独占锁同样只允许一个客户端持久，且不允许 其它客户端共享读取。
 * 分布式锁服务提供以下服务：
 * 1.抢占锁：应用需要上送要抢占的资源URL和锁类型，抢占成功后返回锁监息（URL和失效时间），以便客户端缓存。
 * 2.释放锁：应用可以主动释放持有的锁。
 * 3.锁降级：允许对高等级的锁进行降级，如：从独占锁降级到写锁。
 * 4.锁变更事件通知：在锁状态变更时，通知希望抢占此锁（注册）的客户端。
 */

public class CommonLockServiceImpl implements Runnable, CommonLockService {
	private static final Logger log = LoggerFactory.getLogger(CommonLockServiceImpl.class);
	private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	/**
	 * 共享读锁，允许多个客户端同时持有。
	 */
	protected static final String TYPE_READ = "R";
	/**
	 * 写锁只允许一个客户端持有，但允许其它客户端共享读取
	 */
	protected static final String TYPE_WRITE = "W";
	/**
	 * 独占锁同样只允许一个客户端持久，且不允许 其它客户端共享读取
	 */
	protected static final String TYPE_SINGLE = "S";
	/**
	 * 默认超时时间
	 */
	@Value("${lock.config.timeout:30}")
	private String timeout;
	@Value("${lock.config.delay:300000}")
	private String delay;

	private ScheduledExecutorService scheduler;

	private CommonLockListener commonLockListener;

	@Resource
	LockInfoBiz lockInfoBiz;

	@Override
	public ReplyInfo<Object> list(String appId, String url, String address, String type, String orderBy, int pageNo, int pageSize) {
		if (!checkValidLockType(type)) {
			return new ReplyInfo<>(false,"锁类型不合法！");
		}
		PagedResult<LockInfo> lockInfos = lockInfoBiz.list(appId,url,address,type, orderBy, pageNo, pageSize);
		return new ReplyInfo<>(true,lockInfos);
	}

	@Override
	public ReplyInfo<Object> lock(String uid, String url, String appId, String type, String address) {
		return lock(uid, url, appId, type, address, timeout);
	}

	/**
	 * 加锁
	 * @param url 资源url
	 * @param type 操作类型/锁类型 R-读 W-写 RW读写
	 * @param address 加锁的客户端地址
	 * @return 加锁结果
	 */
	@Override
	public ReplyInfo<Object> lock(String uid, String url, String appId, String type, String address, String timeout) {
		if (!checkValidLockType(type)) {
			return new ReplyInfo<>(false,"锁类型不合法！");
		}
		//检查当前地址是否已经对该资源加了锁
		if (checkIsLocked(appId, url, address)) {
			return new ReplyInfo<>(false,"已对该资源加过锁了...");
		}
		LockInfo lockInfo = new LockInfo();
		//如果允许加锁，则加锁并且返回加锁监息
		if (isAccess(appId, type, url)) {
			try {
				lockInfo.setType(type);
				lockInfo.setLockTime(formatter.format(new Date()));
				lockInfo.setTimeout(timeout);
				lockInfo.setAppId(appId);
				lockInfo.setAddress(address);
				lockInfo.setUrl(url);
				lockInfoBiz.save(lockInfo);
			} catch (DataIntegrityViolationException e){
				log.debug("抢锁失败，已被{"+lockInfo.getAddress()+"}抢锁");
				//加入抢锁列表
				commonLockListener.onLocked(url, appId, type, address);
				return new ReplyInfo<>(false,"抢锁失败，已有互斥的锁");
			}
		} else {
			//加入抢锁列表
			commonLockListener.onLocked(url, appId, type, address);
			return new ReplyInfo<>(false,"抢锁失败，已有互斥的锁");
		}
		return new ReplyInfo<>(true,lockInfo);
	}

	/**
	 * 检查当前资源是否已经被该地址加了锁，一个地址只能对一个资源有一把锁
	 * @param appId 应用id
	 * @param url 资源url
	 * @param address 加锁地址
	 * @return	是否已经有锁
	 */
	private boolean checkIsLocked(String appId, String url, String address) {
		//检查当前地址是否已经加过该资源的锁，有可能加多次读锁，因为读锁可以有多个
		List<LockInfo> lockInfoList = lockInfoBiz.find(appId, url, address, null);
		return !CollectionUtils.isEmpty(lockInfoList);
	}



	/**
	 * 判断是否允许加锁 是否有互斥锁
	 * 已有读锁 - 不能加独占锁，可以加写锁
	 * 已有写锁 - 可加读锁 ，不能加独占锁
	 * 已有独占锁 - 都不可加
	 * key 资源/锁类型
	 * @param appId 操作类型
	 * @param type 锁类型
	 * @param url 资源路径
	 * @return	true/false
	 */
	private boolean isAccess(String appId, String type, String url) {
		//获取互斥锁数组
		String[] mutexs = getMutexTypeArray(type);
		for (String mutex : mutexs) {
			List<LockInfo> lockInfos = lockInfoBiz.find(appId, url,null, mutex);
			if(!CollectionUtils.isEmpty(lockInfos)){
				return false;
			}
		}
		return true;
	}

	/**
	 * 获取互斥锁列表
	 * @param type 锁类型
	 * @return 锁数组
	 */
	private String[] getMutexTypeArray(String type) {
		switch(type){
			case TYPE_READ:
				return new String[]{TYPE_SINGLE};
			case TYPE_WRITE:
				return new String[]{TYPE_SINGLE,TYPE_WRITE};
			default:
				return new String[]{TYPE_WRITE,TYPE_SINGLE};
		}
	}

	/**
	 * 解锁
	 * @param url 资源url
	 * @param appId 应用Id
	 * @param address 地址
	 * @param type 所类型
	 * @return 解锁结果
	 */
	@Override
	public ReplyInfo<Object> unLock(String uid, String url,String appId, String type,String address) {
		if (!checkValidLockType(type)) {
			return new ReplyInfo<>(false,"锁类型不合法！");
		}
		List<LockInfo> lockInfos = lockInfoBiz.find(appId, url, address,type);
		if(CollectionUtils.isEmpty(lockInfos)){
			return new ReplyInfo<>(false,"没有相关联的加锁监息，无需解锁！");
		}
		LockInfo lockInfo = lockInfos.get(0);
		//获取当前资源下的锁并解锁
		lockInfoBiz.delete(lockInfo);
		commonLockListener.onUnLock(lockInfo,getMutexTypeArray(type));
		//如果是写锁，通知对应的读锁持有者更新数据
		if (TYPE_WRITE.equals(type)) {
			commonLockListener.onNotice(lockInfo,TYPE_READ);
		}
		return ReplyInfo.SUCCESS;
	}

	/**
	 * 锁降级
	 * @param url 资源url
	 * @param address 地址
	 * @param appId 应用Id
	 * @param oriType 原锁类型
	 * @param newType 新锁类型
	 * @return 降级后的锁监息
	 */
	@Override
	public ReplyInfo<Object> reduce(String uid, String url, String appId, String address, String oriType, String newType) {
		//判断锁是否合法
		if(!checkValidLockType(oriType) || !checkValidLockType(newType) || checkLockLevel(oriType,newType) <= 0){
			return new ReplyInfo<>(false,"锁类型不合法！");
		}
		//查找原来的锁
		List<LockInfo> lockInfos = lockInfoBiz.find(appId, url, address,oriType);
		if(CollectionUtils.isEmpty(lockInfos)){
			return new ReplyInfo<>(false,"没有找到原加锁监息！");
		}
		LockInfo lockInfo = lockInfos.get(0);
		lockInfo.setType(newType);
		lockInfo.setLockTime(formatter.format(new Date()));
		lockInfoBiz.save(lockInfo);
		return ReplyInfo.SUCCESS;
	}

	@Override
	public ReplyInfo<Object> check(String uid, String url, String appId, String type, String address) {
		if (!checkValidLockType(type)) {
			return new ReplyInfo<>(false,"锁类型不合法！");
		}
		//是否允许加锁
		boolean flg = isAccess(appId, type, url);
		return new ReplyInfo<>(true,flg);
	}

	/**
	 * 检查锁类型是否合法
	 * @param type 锁类型
	 * @return	true-合法,false-不合法
	 */
	private boolean checkValidLockType(String type){
		switch (type) {
			case TYPE_READ:
			case TYPE_WRITE:
			case TYPE_SINGLE:
				return true;
			default:
				return false;
		}
	}
	/**
	 * 判断oriType与newType锁等级
	 * @param oriType 原锁类型
	 * @param newType 新锁类型
	 * @return oriType>newType返回1，oriType=newType返回0，oriType<newType返回-1
	 */
	private int checkLockLevel(String oriType, String newType) {
		switch (oriType){
			case TYPE_READ:
				if(TYPE_READ.equals(newType)){
					return 0;
				}
				return -1;
			case TYPE_WRITE:
				if (TYPE_READ.equals(newType)){
					return 1;
				} else if (TYPE_WRITE.equals(newType)) {
					return 0;
				}
				return -1;
			default:
				if (TYPE_READ.equals(newType) || TYPE_WRITE.equals(newType)){
					return 1;
				}
				return 0;
		}
	}

	/**
	 * 初始化定时参数
	 */
	@PostConstruct
	public void init() {
		scheduler.scheduleWithFixedDelay(this, Long.parseLong(delay), Long.parseLong(delay), TimeUnit.MILLISECONDS);
	}

	/**
	 * 轮询内存中的锁列表，删除超时的锁
	 */
	@Override
	public void run() {
		List<LockInfo> lockInfos = lockInfoBiz.find(null, null, null,null);
		log.debug("扫描超时数据并删除...");
		if(CollectionUtils.isEmpty(lockInfos)){
			return;
		}
		for (LockInfo lockInfo : lockInfos) {
			try {
				Date lockTime = formatter.parse(lockInfo.getLockTime());
				if (DateUtil.dateDiff(new Date(),lockTime, DateUtil.DatePart.SEC) > Long.parseLong(lockInfo.getTimeout())) {
					//超时删除锁
					lockInfoBiz.delete(lockInfo);
					commonLockListener.onUnLock(lockInfo,getMutexTypeArray(lockInfo.getType()));
				}
			} catch (ParseException e) {
				log.debug("日期格式转换错误");
			}
		}
	}


	public ScheduledExecutorService getScheduler() {
		return scheduler;
	}

	public void setScheduler(ScheduledExecutorService scheduler) {
		this.scheduler = scheduler;
	}

	public CommonLockListener getCommonLockListener() {
		return commonLockListener;
	}

	public void setCommonLockListener(CommonLockListener commonLockListener) {
		this.commonLockListener = commonLockListener;
	}
}